﻿#include "https-service.h"

#define CHK_NULL(x)       \
    if ((x) == NULL) {    \
        printf("null\n"); \
    }
#define CHK_ERR(err, s)   \
    if ((err) == -1) {    \
        printf(" -1 \n"); \
    }
#define CHK_SSL(err)      \
    if ((err) == -1) {    \
        printf(" -1 \n"); \
    }

#define SV_CER_FILE "cert/server/server.crt"
#define SV_KEY_FILE "cert/server/server.key"

void nets::HttpsService::init() {
    WORD               wVersionRequested;
    WSADATA            wsaData;
    // SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(CAFILE));
    wVersionRequested = MAKEWORD(2, 2);
    int ret = WSAStartup(wVersionRequested, &wsaData);
	if (ret != 0) {
		printf("WSAStartup failed with error: %d\n", ret);
		exit(EXIT_FAILURE);
    }

    SSL_library_init();
    SSL_load_error_strings();
    OpenSSL_add_all_algorithms();

    // 初始化服务器默认配置
    if (this->sv_conf == nullptr) {
        this->sv_conf            = new server_conf;
        this->sv_conf->host      = INADDR_ANY;
        this->sv_conf->port      = 443;
        this->sv_conf->is_ssl    = 1;
        this->sv_conf->cert_file = SV_CER_FILE;
        this->sv_conf->key_file  = SV_KEY_FILE;
    }
}

SSL_CTX *nets::HttpsService::create_context() {
    const SSL_METHOD *method = SSLv23_server_method();
    SSL_CTX          *ctx    = SSL_CTX_new(method);
    if (!ctx) {
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }
    return ctx;
}

void nets::HttpsService::configure_context(SSL_CTX *ctx) {
    if (SSL_CTX_use_certificate_file(ctx, this->sv_conf->cert_file, SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }
    if (SSL_CTX_use_PrivateKey_file(ctx, this->sv_conf->key_file, SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }
    if (!SSL_CTX_check_private_key(ctx)) {
        fprintf(stderr, "Private key does not match the public certificaten");
        exit(EXIT_FAILURE);
    }
}

int *nets::HttpsService::worker() {
    int                err_code = -1;
    SOCKET             client_socket;
    int                err;
    SSL               *ssl;
    X509              *client_cert;
    char              *str;
    char               buf[1024];

    while (true) {
        {
            // 获取锁
            unique_lock<mutex> lock(mtx);
            // 等待条件变量通知
            cv.wait(lock, [this] { return !this->clients.empty(); });
            ssl = this->clients.front();
            this->clients.pop();
        }


        client_socket = SSL_get_fd(ssl);

        // set to non-blocking
        /*unsigned long iMode = 1;
        if (ioctlsocket(s, FIONBIO, (unsigned long *)&iMode) < 0) {
            closesocket(s);
        }*/
        SSL_set_accept_state(ssl);
        // err = SSL_connect(ssl);
        // err = SSL_do_handshake(ssl);
        err = SSL_accept(ssl);

        // non-blocking handshake
        // while (true) {
        //     err = SSL_do_handshake(ssl);
        //     if (err == SSL_ERROR_SSL) {
        //         // 握手成功
        //         break;
        //     }
        //     int ssl_err = SSL_get_error(ssl, err);
        //     if (ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE) {
        //         // 继续等待
        //         continue;
        //     } else {
        //         // 其他错误，打印日志
        //         ERR_print_errors_fp(stderr);
        //         break;
        //     }
        // }

        printf("----------------------------------------------------------------------\n");

        if (err < 0) {
            printf("err str: %s\n", SSL_state_string_long(ssl));

            printf("ssl get-err: %d\n", SSL_get_error(ssl, err));
            if (SSL_get_error(ssl, err) == SSL_ERROR_WANT_READ) {
                printf("ssl get-err: SSL_ERROR_WANT_READ\n");
            }

            ERR_print_errors_fp(stderr);
            printf("ssl accept-err: %d\n", err);
            free(*ssl, client_socket);
            // return &err_code;
        } else {
            printf("SSL connection using %s\n", SSL_get_cipher(ssl));
            client_cert = SSL_get_peer_certificate(ssl);
            if (client_cert != NULL) {
                printf("Client certificate:\n");
                str = X509_NAME_oneline(X509_get_subject_name(client_cert), 0, 0);
                CHK_NULL(str);
                printf("\t subject: %s\n", str);
                OPENSSL_free(str);
                str = X509_NAME_oneline(X509_get_issuer_name(client_cert), 0, 0);
                CHK_NULL(str);
                printf("\t issuer: %s\n", str);
                OPENSSL_free(str);
                X509_free(client_cert);
            } else
                printf("Client does not have certificate.\n");

            memset(buf, 0, 1024);
            err = SSL_read(ssl, buf, sizeof(buf) - 1);
            if (err < 0) {
                printf("ssl read err\n");
                free(*ssl, client_socket);
                // return &err_code;
            } else {
                printf("get : %s\n", buf);
#if 1
                buf[err] = '\0';
                err      = SSL_write(ssl, "I hear you.", strlen("I hear you."));
                CHK_SSL(err);
#endif
                free(*ssl, client_socket);
            }
        }
    }
}

int nets::HttpsService::run(const server_conf *conf) {
    int                err;
    SOCKET             server_socket, client_socket;
    struct sockaddr_in service;
    SSL_CTX           *ctx;
    SSL               *ssl;

    this->sv_conf = (server_conf *)conf;
    this->init();
    ctx = this->create_context();
    this->configure_context(ctx);

    // int s_server_verify = SSL_VERIFY_NONE;
    int s_server_verify = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT |
                          SSL_VERIFY_CLIENT_ONCE;
    // SSL_CTX_set_verify(ctx, s_server_verify, verify_callback);

    // SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);

    server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (server_socket < 0)
        return -1;

    char opt = 1;
    if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }

    service.sin_family      = AF_INET;
    service.sin_addr.s_addr = this->sv_conf->host || INADDR_ANY;
    // service.sin_addr.s_addr = inet_addr("192.168.6.173");
    service.sin_port = htons(this->sv_conf->port);
    if (bind(server_socket, (SOCKADDR *)&service, sizeof(service)) == SOCKET_ERROR) {
        perror("bind failed");
        closesocket(server_socket);
        return -1;
    }
    if (listen(server_socket, 1) == SOCKET_ERROR)
        perror("Error listening on socket");

    printf("receiving.....\n");

    /*lock_cs    = OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
    lock_count = OPENSSL_malloc(CRYPTO_num_locks() * sizeof(long));
    for (i = 0; i < CRYPTO_num_locks(); i++) {
        lock_count[i] = 0;
        pthread_mutex_init(&(lock_cs[i]), NULL);
    }*/
    // CRYPTO_set_id_callback((unsigned long (*)())this_thread::get_id);
    // CRYPTO_set_locking_callback((void (*)())pthreads_locking_callback);

    std::vector<std::thread> workers;
    for (int i = 0; i < 4; i++) {
        workers.emplace_back(thread(&HttpsService::worker, this));
    }

    struct sockaddr_in address;
    int                addrlen = sizeof(address);

    while (true) {
        struct timeval tv;
        fd_set         fdset;
        tv.tv_sec  = 1;
        tv.tv_usec = 0;
        FD_ZERO(&fdset);
        FD_SET(server_socket, &fdset);
        // TODO epoll?
        // select(s + 1, &fdset, NULL, NULL, (struct timeval *)&tv);
        if (FD_ISSET(server_socket, &fdset)) {
            client_socket = accept(server_socket, (SOCKADDR *)&address, &addrlen);
            ssl           = SSL_new(ctx);
            CHK_NULL(ssl);
            err = SSL_set_fd(ssl, client_socket);
            if (err > 0) {
                if (client_socket == INVALID_SOCKET) {
                    // Handle error
                    closesocket(client_socket); // Close the client socket when done
                    continue;
                }
                // Successfully accepted a connection
                // Here you can handle the client socket, e.g., read/write data
                {
                    lock_guard<mutex> lock(mtx);
                    clients.push(ssl);
                }
                cv.notify_one();
            } else
                continue;
        }
    }

    free(*ssl, server_socket);

    return 0;
}

int nets::HttpsService::free(SSL &ssl, SOCKET s) {
    SSL_free(&ssl);
    closesocket(s);
    return 0;
}

int nets::HttpsService::SSL_CTX_use_PrivateKey_file_pass(SSL_CTX *ctx, char *filename, char *pass) {
    EVP_PKEY *pkey = NULL;
    BIO      *key  = NULL;
    key            = BIO_new(BIO_s_file());
    BIO_read_filename(key, filename);
    pkey = PEM_read_bio_PrivateKey(key, NULL, NULL, pass);
    if (pkey == NULL) {
        printf("PEM_read_bio_PrivateKey err");
        return -1;
    }
    if (SSL_CTX_use_PrivateKey(ctx, pkey) <= 0) {
        printf("SSL_CTX_use_PrivateKey err\n");
        return -1;
    }
    BIO_free(key);
    return 1;
}
